package com.ruoyi.network.server;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.ruoyi.common.utils.Threads;
import com.ruoyi.network.common.Constant;
import com.ruoyi.network.common.NioSocket;
import com.ruoyi.network.config.AbstractHelper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ServerHelper extends AbstractHelper {
	// 监控线程
	public static ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);

	public static void start() {
		monitor.scheduleWithFixedDelay(() -> {
			clear();
		}, 15, 15, TimeUnit.SECONDS);
	}

	public synchronized static void distory() {
		// monitor.shutdown();
		Threads.shutdownAndAwaitTermination(monitor);
		for (NioSocket clientSocket : ServerContext.all.keySet()) {
			try {
				close(clientSocket);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	// synchronized是为了保证 dispatch 与 clear必须是互斥操作
	private synchronized static void clear() {
		try {
			List<NioSocket> timeouts = new LinkedList<>();
			log.debug("开始扫描失效与超时连接");
			Date start = new Date();
			for (NioSocket clientSocket : ServerContext.all.keySet()) {
				Date lastTime = ServerContext.all.get(clientSocket);
				Date now = new Date();
				Date createTime = clientSocket.getCreateTime();
				boolean isOpen = clientSocket.getSocketChannel().isOpen();
				boolean isReadTimeout = comparseSec(lastTime, now) >= Constant.READ_TIMEOUT;
				boolean isBindTimeout = !clientSocket.isBind() && comparseSec(createTime, now) >= Constant.BIND_TIMEOUT;
				if (!isOpen || isReadTimeout || isBindTimeout) {
					timeouts.add(clientSocket);
				}

			}
//			log.debug(
//					"超时连接找到" + timeouts.size() + "个,用时 : " + (new Date().getTime() - start.getTime()) + "毫秒");
//			log.debug("开始清除失效与超时连接");
			for (NioSocket socket : timeouts) {
				try {
					close(socket);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
//		show();

	}

	private static void close(NioSocket socket1) throws IOException {
		SocketChannel channel1 = socket1.getSocketChannel();
		NioSocket socket2 = ServerContext.pairs.get(channel1);
		if (socket2 != null) {
			try {
				SocketChannel channel2 = socket2.getSocketChannel();
				ServerContext.pairs.remove(channel1);
				ServerContext.all.remove(socket2);
				ServerContext.inners.remove(socket2);
				ServerContext.outers.remove(socket2);
				channel2.close();
				ServerContext.pairs.remove(channel2);
				socket2.getQueue().clear();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		try {
			ServerContext.all.remove(socket1);
			ServerContext.inners.remove(socket1);
			ServerContext.outers.remove(socket1);
			socket1.getQueue().clear();
			// 必须要将close放在最后处理,不然Map中找不到对应的value
			channel1.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// synchronized是为了保证 dispatch 与 clear必须是互斥操作
	private synchronized static void dispatch(Selector selector, NioSocket outerSocket) throws Exception {
		if (!outerSocket.isBind() && outerSocket.getSocketChannel().isOpen()) {
			Iterator<NioSocket> it = ServerContext.inners.keySet().iterator();
			while (it.hasNext()) {
				NioSocket innerSocket = it.next();
				if (!innerSocket.isBind() && outerSocket.getSocketChannel().isOpen() && !outerSocket.isBind()) {
					ServerContext.pairs.put(outerSocket.getSocketChannel(), innerSocket);
					ServerContext.pairs.put(innerSocket.getSocketChannel(), outerSocket);
					outerSocket.setBind(true);
					innerSocket.setBind(true);
					try {
						// 绑定完毕后才能注册为读,以保证报文顺序正确
						long start = System.currentTimeMillis();
						// register()方法是阻塞的,必须要selector唤醒才能返回
						SelectionKey outerKey = outerSocket.getSocketChannel().register(outerSocket.getSelector(),
								SelectionKey.OP_READ);
						outerSocket.setSelectionKey(outerKey);
						SelectionKey innerKey = innerSocket.getSocketChannel().register(innerSocket.getSelector(),
								SelectionKey.OP_READ);
						innerSocket.setSelectionKey(innerKey);
						long now = System.currentTimeMillis();
						log.debug("bind success ... " + (now - start));
						break;
					} catch (Exception e) {
						e.printStackTrace();
						try {
							close(outerSocket);
						} catch (IOException e1) {
							e1.printStackTrace();
						}
					}
				}
			}
		}
	}

	public static void close(SelectionKey key) {
		try {
			if (key != null) {
				key.cancel();
				SocketChannel channel1 = (SocketChannel) key.channel();
				if (channel1 != null) {
					close(channel1);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void close(SocketChannel channel1) throws IOException {
		NioSocket socket2 = ServerContext.pairs.get(channel1);
		if (socket2 != null) {
			try {
				SocketChannel channel2 = socket2.getSocketChannel();
				NioSocket socket1 = ServerContext.pairs.get(channel2);

				ServerContext.pairs.remove(channel2);
				ServerContext.all.remove(socket2);
				ServerContext.inners.remove(socket2);
				ServerContext.outers.remove(socket2);
				if (socket1 != null) {
					ServerContext.pairs.remove(channel1);
					ServerContext.all.remove(socket1);
					ServerContext.inners.remove(socket1);
					ServerContext.outers.remove(socket1);
				}
				// 必须要将close放在最后处理,不然Map中找不到对应的value
				channel2.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		try {
			channel1.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void handleRead(String name, Selector selector, SelectionKey key) throws Exception {
		SocketChannel fromChannel = (SocketChannel) key.channel();
		NioSocket toSocket = ServerContext.pairs.get(fromChannel);
		if (toSocket != null) {// 绑定了转发Socket才能读数据
			// 创建ByteBuffer，并开辟一个1M的缓冲区,使用单例模式优化
			// ByteBuffer buffer = ByteBuffer.allocate(Constant.BYTE_BUFFER_SIZE);
			// //增加缓冲区大小以处理更大的数据包
			ByteBuffer buffer = ByteBuffer.allocateDirect(Constant.BYTE_BUFFER_SIZE); // 使用直接缓冲区,这可以减少JVM堆内存的复制操作，提高IO性能
			// 读取请求码流，返回读取到的字节数
			int readBytes = fromChannel.read(buffer);
			// 读取到字节，对字节进行编解码
			if (readBytes > 0) {
				NioSocket fromSocket = ServerContext.pairs.get(toSocket.getSocketChannel());
				// 更新通信时间
				ServerContext.all.put(toSocket, new Date());
				ServerContext.all.put(fromSocket, new Date());
				// 将数据存放在队列中,注册写事件,待channel可写时再发送
				toSocket.getQueue().put(buffer);
				// channel注册写事件(注意,是需要写的Channel注册)
				SelectionKey toSocketKey = toSocket.getSelectionKey();
				toSocketKey.interestOps(toSocketKey.interestOps() | SelectionKey.OP_WRITE);

			} else if (readBytes < 0) {
				ServerHelper.close(key);
			}
		}

		// 在带宽不好/接收端处理不过来的情况下,数据传送过快将导致写阻塞
		// 解决方案一:控制转发速度
		// Thread.sleep(10L);
		// 解决方案二:调整发送及接收缓冲区大小(TCP协议滑动窗口)
		// SocketChannel.setOption(StandardSocketOptions.SO_SNDBUF, 16 * 1024 * 1024);
		// SocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024 * 1024);
		// 解决方案三:使用BIO实现;BIO同步阻塞式,相当于从底层机制控制了流量
		// 由于BIO模型是阻塞的，服务器端的发送操作通常会受到操作系统套接字缓冲区大小的限制，以及网络带宽和延迟的限制。
		// 一旦发送缓冲区满了，发送操作就会阻塞，直到缓冲区中有足够的空间可以继续发送数据

	}

	public static void handleAccept(String name, Selector selector, SelectionKey key, SocketChannel socketChannel)
			throws Exception {
		// 设置为非阻塞的
		socketChannel.configureBlocking(false);
		// 注册为读,绑定完毕后才能注册为读,以保证报文顺序正确
		// socketChannel.register(selector, SelectionKey.OP_READ);
		NioSocket socket = new NioSocket(name, selector, socketChannel, false);
		ServerContext.all.put(socket, new Date());
		if (name.equals("外网服务")) {
			log.debug("【Server】外网连接" + socketChannel.getRemoteAddress() + " 连接成功...");
			ServerContext.outers.put(socket, new Date());
		} else {
			log.debug("【Server】内网连接" + socketChannel.getRemoteAddress() + " 连接成功...");
			ServerContext.inners.put(socket, new Date());
		}
		// 不管连接情况,统一对所有未绑定的对象同步非阻塞分配,
		Iterator<NioSocket> it = ServerContext.outers.keySet().iterator();
		while (it.hasNext()) {
			NioSocket tmp = it.next();
			dispatch(selector, tmp);
		}
		// show();
	}

	public static void show() {
		log.debug("pairs " + ServerContext.pairs.size());
		log.debug("all " + ServerContext.all.size());
		log.debug("inners " + ServerContext.inners.size());
		log.debug("outers " + ServerContext.outers.size());
	}

	public static void handleWrite(String name, Selector selector, SelectionKey key) throws Exception {
		SocketChannel toChannel = (SocketChannel) key.channel();
		NioSocket fromSocket = ServerContext.pairs.get(toChannel);
		if (fromSocket != null) {// 绑定了转发Socket才能读数据
			NioSocket toSocket = ServerContext.pairs.get(fromSocket.getSocketChannel());
			BlockingQueue<ByteBuffer> blockingQueue = toSocket.getQueue();
			if (blockingQueue != null) {
				ByteBuffer buffer = blockingQueue.peek();// 取出队列首个元素,但不删除
				if (buffer != null) {
					// 切换为写模式
					buffer.flip();
					// 如果网络状况不佳，或者对端服务器没有及时接收数据，SocketChannel的写操作可能会阻塞。如果写操作持续阻塞，上传线程可能会卡死
					int isOk = toSocket.getSocketChannel().write(buffer);
					if (isOk == 0) {
						// 切换为读模式,等待下一次轮询
						buffer.flip();
					} else {
						buffer.clear();
						blockingQueue.poll();// 取出队列首个元素,并删除
					}
				}
			}
		}

	}

}
